package registry

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"sync"
)

func RegisterService(r Registration) error {
	serviceUpdateUrl, err := url.Parse(r.ServiceUpdateUrl)
	if err != nil {
		return err
	}
	http.Handle(serviceUpdateUrl.Path, &serviceUpdateHandler{})
	fmt.Println("Http Server Started Url:", serviceUpdateUrl.Path)
	buf := new(bytes.Buffer)
	enc := json.NewEncoder(buf)
	err = enc.Encode(r)

	if err != nil {
		return err
	}
	res, err := http.Post(ServiceUrl, "application/json", buf)
	if err != nil {
		return err
	}

	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to register service. Registry service responsed code : %v", res.StatusCode)
	}

	return nil
}

type serviceUpdateHandler struct{}

func (suh serviceUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	dec := json.NewDecoder(r.Body)
	var p patch
	err := dec.Decode(&p)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	fmt.Println("Updated received ", p)
	provider.Update(p)
}

func ShutdownService(url string) error {
	req, err := http.NewRequest(http.MethodDelete, ServiceUrl, bytes.NewBuffer([]byte(url)))
	if err != nil {
		return err
	}
	req.Header.Add("Content-Type", "text/plain")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to deregistry service. deregistry service responsed code : %v", resp.StatusCode)
	}
	return nil
}

type providers struct {
	Services map[ServiceName][]string
	Mutex    *sync.RWMutex
}

var provider = providers{
	Services: make(map[ServiceName][]string),
	Mutex:    new(sync.RWMutex),
}

func (p providers) Update(pat patch) {
	p.Mutex.Lock()
	defer p.Mutex.Unlock()

	for _, patchEntry := range pat.Added {
		if _, ok := p.Services[patchEntry.Name]; !ok {
			p.Services[patchEntry.Name] = make([]string, 0)
		}
		p.Services[patchEntry.Name] = append(p.Services[patchEntry.Name], patchEntry.Url)
	}

	for _, patchEntry := range pat.Removed {
		if providerUrls, ok := p.Services[patchEntry.Name]; ok {
			for i := range providerUrls {
				if providerUrls[i] == patchEntry.Url {
					p.Services[patchEntry.Name] = append(providerUrls[:i], providerUrls[i+1:]...)
				}
			}
		}
	}
}

func (p providers) get(name ServiceName) (string, error) {
	providers, ok := p.Services[name]
	if !ok {
		return "", fmt.Errorf("no providers avaliable for service %v", name)
	}
	// 这里因为是demo只有一个依赖，偷懒这样写了
	return providers[0], nil
}

func GetProvider(name ServiceName) (string, error) {
	return provider.get(name)
}
